import six
import operator
from collections import OrderedDict
from functools import reduce
from django.db.models import Q
from django.db.models.constants import LOOKUP_SEP
from django_filters import utils, CharFilter
from django_filters.rest_framework import DjangoFilterBackend
from django_filters.utils import get_model_field
from rest_framework.filters import BaseFilterBackend

from common.models.system import Dept


def get_child_dept_ids(pids: list):
    """
    递归获取部门的所有下级部门
    :param pids: 父级部门id
    :return:
    """
    if len(pids) == 0:
        return []
    elif len(pids) == 1:
        depts = list(Dept.objects.filter(parent_id=pids[0]).values_list('id', flat=True))
    else:
        depts = list(Dept.objects.filter(parent_id__in=pids).values_list('id', flat=True))

    if not depts:
        return []
    depts.extend(get_child_dept_ids(depts))
    return depts


class DataLevelPermissionsFilter(BaseFilterBackend):
    """
    数据 级权限过滤器
    0. 判断过滤的数据是否有创建人 "creator" 字段,没有则返回全部
    1. 获取用户的部门id，没有部门则返回空
    2. 自定数据权限 获取部门，根据部门过滤
    3. 全部数据权限
    """
    def get_belong_field_name(self, model):
        """获取数据筛选字段"""
        if model._meta.model_name == 'dept':
            return 'id'
        if hasattr(model, 'dept_belong_id'):
            return 'dept_belong_id'
        elif hasattr(model, 'project'):
            return 'project__dept_belong_id'
        elif hasattr(model, 'group'):
            return 'group__dept_belong_id'
        else:
            return None

    def get_request_filter_id(self, request):
        """获取请求中的数据筛选id"""
        request_filter_id = request.GET.get('dept_id') or request.GET.get('dept')
        if request_filter_id and request_filter_id.isdigit():
            return int(request_filter_id)
        else:
            return None

    def belong_filter(self, queryset, belong_ids):
        """根据数据所属id 拼接查询语句"""
        belong_field_name = self.get_belong_field_name(queryset.model)
        if not belong_field_name:
            return queryset
        if len(belong_ids) > 1:
            queries = [Q(**{'{}__in'.format(belong_field_name): belong_ids})]
        elif not belong_ids[0]:
            return queryset.none()
        else:
            queries = [Q(**{belong_field_name: belong_ids[0]})]

        conditions = [reduce(operator.and_, queries)]
        return queryset.filter(reduce(operator.and_, conditions))

    def filter_queryset(self, request, queryset, view):
        """认证数据权限"""
        # 请求参数
        request_filter_id = self.get_request_filter_id(request)
        # 获取用户的部门id
        user_belong_id = getattr(request.user, 'dept_id', None)
        # 超级管理员返回全部数据
        if request.user.is_superuser:
            if request_filter_id:
                belong_ids = [request_filter_id]
                return self.belong_filter(queryset, belong_ids)
            return queryset

        # 如果用户没有关联角色, 默认返回none
        if not hasattr(request.user, 'role'):
            return queryset.none()

        # 根据所有角色 获取所有权限范围
        # (0, "仅本人数据权限"),
        # (1, "本部门及以下数据权限"),
        # (2, "自定义数据权限"),
        # (3, "全部数据权限"),
        # (4, "本部门数据权限")
        role_list = request.user.role.values_list('data_range', flat=True)
        data_scope_list = list(set(role_list))  # 权限范围列表
        # 没有配置数据权限范围，返回None
        if not data_scope_list:
            return queryset.none()

        # 【全部数据权限】
        if 3 in data_scope_list:
            if request_filter_id:
                return self.belong_filter(queryset, [request_filter_id])
            else:
                return queryset
        # 【自定义权限】
        if 2 in data_scope_list:
            belong_ids = request.user.role.values_list('dept__id', flat=True)
            if not request_filter_id:
                return self.belong_filter(queryset, belong_ids)
            elif request_filter_id in belong_ids:
                return self.belong_filter(queryset, [request_filter_id])
            else:
                return queryset.none()

        # 【本部门及以下数据权限】
        if 1 in data_scope_list:
            dept_id = request_filter_id or user_belong_id
            dept_ids = [dept_id]
            dept_ids.extend(get_child_dept_ids(dept_ids))
            return self.belong_filter(queryset, dept_ids)

        # 【本部门数据权限】
        if 4 in data_scope_list:
            if request_filter_id and request_filter_id != user_belong_id:
                return queryset.none()
            else:
                return self.belong_filter(queryset, [user_belong_id])

        # 【仅本人数据权限】考虑到用户会变部门，只能看当前用户所在的部门数据
        if 0 in data_scope_list:
            new_queryset = queryset.filter(creator_id=request.user.id)
            if request_filter_id and request_filter_id != user_belong_id:
                return new_queryset.none()
            else:
                return self.belong_filter(new_queryset, [user_belong_id])

        return queryset.none()


class CustomDjangoFilterBackend(DjangoFilterBackend):
    lookup_prefixes = {
        "^": "istartswith",
        "=": "iexact",
        "@": "search",
        "$": "iregex",
        "~": "icontains",
    }

    def construct_search(self, field_name, lookup_expr=None):
        lookup = self.lookup_prefixes.get(field_name[0])
        if lookup:
            field_name = field_name[1:]
        else:
            lookup = lookup_expr
        if field_name.endswith(lookup):
            return field_name
        return LOOKUP_SEP.join([field_name, lookup])

    def find_filter_lookups(self, orm_lookups, search_term_key):
        for lookup in orm_lookups:
            # if lookup.find(search_term_key) >= 0:
            new_lookup = lookup.split("__")[0]
            # 修复条件搜索错误 bug
            if new_lookup == search_term_key:
                return lookup
        return None

    def get_filterset_class(self, view, queryset=None):
        """
        Return the `FilterSet` class used to filter the queryset.
        """
        filterset_class = getattr(view, "filterset_class", None)
        filterset_fields = getattr(view, "filterset_fields", None)

        if filterset_class is None and hasattr(view, "filter_class"):
            utils.deprecate(
                "`%s.filter_class` attribute should be renamed `filterset_class`."
                % view.__class__.__name__
            )
            filterset_class = getattr(view, "filter_class", None)

        if filterset_fields is None and hasattr(view, "filter_fields"):
            utils.deprecate(
                "`%s.filter_fields` attribute should be renamed `filterset_fields`."
                % view.__class__.__name__
            )
            filterset_fields = getattr(view, "filter_fields", None)

        if filterset_class:
            filterset_model = filterset_class._meta.model

            # FilterSets do not need to specify a Meta class
            if filterset_model and queryset is not None:
                assert issubclass(
                    queryset.model, filterset_model
                ), "FilterSet model %s does not match queryset model %s" % (
                    filterset_model,
                    queryset.model,
                )

            return filterset_class

        if filterset_fields and queryset is not None:
            MetaBase = getattr(self.filterset_base, "Meta", object)

            class AutoFilterSet(self.filterset_base):
                @classmethod
                def get_filters(cls):
                    """
                    Get all filters for the filterset. This is the combination of declared and
                    generated filters.
                    """

                    # No model specified - skip filter generation
                    if not cls._meta.model:
                        return cls.declared_filters.copy()

                    # Determine the filters that should be included on the filterset.
                    filters = OrderedDict()
                    fields = cls.get_fields()
                    undefined = []

                    for field_name, lookups in fields.items():
                        field = get_model_field(cls._meta.model, field_name)
                        from django.db import models
                        from timezone_field import TimeZoneField

                        # 不进行 过滤的model 类
                        if isinstance(field, (models.JSONField, TimeZoneField)):
                            continue
                        # warn if the field doesn't exist.
                        if field is None:
                            undefined.append(field_name)
                        # 更新默认字符串搜索为模糊搜索
                        if isinstance(field, (models.CharField,)) and \
                                filterset_fields == '__all__' and lookups == ['exact']:
                            lookups = ['icontains']

                        for lookup_expr in lookups:
                            filter_name = cls.get_filter_name(field_name, lookup_expr)

                            # If the filter is explicitly declared on the class, skip generation
                            if filter_name in cls.declared_filters:
                                filters[filter_name] = cls.declared_filters[filter_name]
                                continue

                            filters[filter_name] = cls.filter_for_field(field, field_name, lookup_expr)

                    # Allow Meta.fields to contain declared filters *only* when a list/tuple
                    if isinstance(cls._meta.fields, (list, tuple)):
                        undefined = [
                            f for f in undefined if f not in cls.declared_filters
                        ]

                    if undefined:
                        raise TypeError(
                            "'Meta.fields' must not contain non-model field names: %s"
                            % ", ".join(undefined)
                        )

                    # Add in declared filters. This is necessary since we don't enforce adding
                    # declared filters to the 'Meta.fields' option
                    filters.update(cls.declared_filters)
                    return filters

                class Meta(MetaBase):
                    model = queryset.model
                    fields = filterset_fields

            return AutoFilterSet

        return None

    def filter_queryset(self, request, queryset, view):
        filterset = self.get_filterset(request, queryset, view)
        if filterset is None:
            return queryset
        if filterset.__class__.__name__ == "AutoFilterSet":
            queryset = filterset.queryset
            orm_lookups = list(filterset.filters)
            for search_field in filterset.filters:
                if isinstance(filterset.filters[search_field], CharFilter):
                    orm_lookups.append(
                        self.construct_search(six.text_type(search_field), filterset.filters[search_field].lookup_expr)
                    )
                else:
                    orm_lookups.append(search_field)
            conditions = []
            queries = []
            for search_term_key in filterset.data.keys():
                orm_lookup = self.find_filter_lookups(orm_lookups, search_term_key)
                if not orm_lookup:
                    continue
                if not filterset.data[search_term_key]:
                    continue
                query = Q(**{orm_lookup: filterset.data[search_term_key]})
                queries.append(query)
            if len(queries) > 0:
                conditions.append(reduce(operator.and_, queries))
                queryset = queryset.filter(reduce(operator.and_, conditions))
                return queryset
            else:
                return queryset

        if not filterset.is_valid() and self.raise_exception:
            raise utils.translate_validation(filterset.errors)
        return filterset.qs

    def get_filterset_kwargs(self, request, queryset, view):
        return {
            # 'data': request.query_params if request.method == 'GET' else request.data,
            'data': request.query_params,
            'queryset': queryset,
            'request': request,
        }
